package whf.framework.web.servlet;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

import whf.framework.entity.Entity;
import whf.framework.entity.TreeNode;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.meta.MetaFactory;
import whf.framework.security.UserContext;
import whf.framework.service.ITreeServiceRootDependencyProperty;
import whf.framework.service.Service;
import whf.framework.service.TreeService;
import whf.framework.util.BeanFactory;
import whf.framework.util.ExpireableMap;
import whf.framework.util.ParamUtils;
import whf.framework.util.PermissionUtils;
import whf.framework.util.StringUtils;
import whf.framework.util.Utils;
import whf.framework.web.WebContextHolder;

/**
 * @author wanghaifeng
 * 输出树状的xml
 *
 */
@SuppressWarnings(value="unchecked")
public class TreeXmlServlet extends HttpServlet{
	private static Log log = LogFactory.getLog(TreeXmlServlet.class);
	
	private static Map<String, Document> cache = new ExpireableMap<String, Document>(1000*60*10L);

	private boolean cacheable;

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		this.cacheable = "true".equalsIgnoreCase(config.getInitParameter("cacheable"));
		cache.put("EMPTY", new Document(new Element("TreeNode")));
	}
	
	@Override
	public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/xml; charset=UTF-8");
		String serviceName = request.getParameter("service");
		String queryString = request.getParameter("queryString");
		boolean lazy = ParamUtils.getBooleanParameter(request, "lazy");
		boolean ignoreEmptyParent = ParamUtils.getBooleanParameter(request, "ignoreEmptyParent", false);
		boolean onlyroot = ParamUtils.getBooleanParameter(request, "onlyroot", false);
		long rootId = ParamUtils.getLongParameter(request, "root", 0);
		String checkedId = request.getParameter("checkedId");
		
		if(queryString == null) queryString = "";
		
		Document doc = null;
		if(this.cacheable && !lazy){
			//String cacheId = serviceName+":"+queryString+":"+rootId;
			//doc = cache.get(cacheId);
			if(doc == null){
				TreeXmlUtils utils = new TreeXmlUtils(rootId, serviceName, queryString, checkedId, request);
				try{
					doc = utils.buildDocument();
					//cache.put(cacheId, doc);
				}catch(Exception e){
					log.error(this, e);
				}
			}
		} else {
			TreeXmlUtils utils = new TreeXmlUtils(rootId, serviceName, queryString, checkedId, request, lazy, ignoreEmptyParent, onlyroot);
			try{
				doc = utils.buildDocument();
			}catch(Exception e){
				log.error(this, e);
			}
		}
		if(doc == null) 
			doc = cache.get("EMPTY");
		OutputStream out = response.getOutputStream();
		//comment bacause of jdom version
		//XMLOutputter outputer = new XMLOutputter("",true);
		XMLOutputter outputer = new XMLOutputter();
		outputer.output(doc, out);
//		outputer.setEncoding("gbk");
		//outputer.output(doc, new java.io.FileOutputStream("e:\\1.xml"));
	}

	/**
	 * @author wanghaifeng
	 * @create Aug 11, 2006 11:46:46 PM
	 * 
	 */
	class TreeXmlUtils{
		private long rootId ;
		private String serviceName;
		private String queryString;
		private Service service;
		private boolean lazy;		// 惰性加载子菜单项
		private HttpServletRequest req;
		private UserContext userContext;
		private boolean ignoreEmptyParent;
		private boolean onlyroot;
		private String[] checkedIds;
		
		private Map<Long, Boolean> childrenElements = null;	//<TreeItem.id, ChildrenItems.Count == 0>
		
		private String encodeURL(){
			Map<String, String> props = Utils.newHashMap();
			props.put("service", this.serviceName);
			props.put("queryString", this.queryString);
			props.put("lazy", String.valueOf(this.lazy));
			return StringUtils.buildURLString(req.getContextPath()+"/servlet/TreeXml.xml", props);
		}
		
		private boolean hasPermission(Object target){
			return PermissionUtils.hasPermission(this.userContext, target);
		}
		
		public TreeXmlUtils(long rootId, String serviceName, String queryString, String checkedId,
				HttpServletRequest req, boolean lazy, boolean ignoreEmptyParent, boolean onlyroot){
			this.rootId = rootId;
			this.serviceName = serviceName;
			this.queryString = queryString;
			this.lazy = lazy;
			this.ignoreEmptyParent = ignoreEmptyParent;
			this.onlyroot = onlyroot;
			this.req = req;
			if(!StringUtils.isEmpty(checkedId)){
				this.checkedIds = StringUtils.split(checkedId, ",");
			}
			if(this.lazy) {
				this.childrenElements = Utils.newHashMap();
			}
			try{
				this.service = BeanFactory.getService(this.serviceName);
				this.userContext = WebContextHolder.getWebContext().getUserContext();
			}catch(Exception e){
				log.error(this, e);
				throw new IllegalArgumentException(e);
			}
		}
		
		public TreeXmlUtils(long rootId, String serviceName, String queryString, String checkedId, HttpServletRequest req){
			this(rootId, serviceName, queryString, checkedId,req, false, false, false);
		}
		
		/**
		 * 生成树状需要的xml文件
		 * @modify wanghaifeng Sep 10, 2006 11:49:34 PM
		 * @return
		 * @throws Exception
		 */
		public Document buildDocument() throws Exception{
			Element root = new Element("TreeNode");
			root.setAttribute("text", "根");
			root.setAttribute("value", "0");
			if(this.service instanceof TreeService){ //对于树状服务对象获取树型对象结构
				if(this.rootId != 0){
					TreeNode rootItem = (TreeNode)this.service.findByPrimaryKey(new Long(this.rootId));
					if(this.onlyroot){
						root.addContent(this.buildElement(rootItem, 0, 0));		//取第一层数据
					} else {
						if(this.hasPermission(rootItem)){
							Element ele = this.buildElement(rootItem, 1, 0);
							if(ele != null)
								root = ele;
						}
					}
				} else {
					if(this.service instanceof ITreeServiceRootDependencyProperty){
						ITreeServiceRootDependencyProperty sd = (ITreeServiceRootDependencyProperty)this.service;
						String propertyName = sd.getDependencyProperty();
						Class propertyType = sd.getDependencyPropertyType();
						Service dependService = BeanFactory.getService(MetaFactory.findByModelClass(propertyType));
						List<Entity> roots = dependService.findAll();	//依赖的对象
						for(Entity dependBO: roots){
							List list = this.service.find("t.parent is null and t."+propertyName+"=? order by t.name", dependBO);		//依赖对象下的所有根结点
							if(list.size() == 0) continue;	//如果是空忽略
							Element dependRootEle = new Element("TreeNode");
							dependRootEle.setAttribute("text", dependBO.getName() == null?"--":dependBO.getName());
							dependRootEle.setAttribute("value", String.valueOf(dependBO.getId() * -1));
							for(Iterator it=list.iterator(); it.hasNext(); ){
								TreeNode item = (TreeNode)it.next();
								if(!this.hasPermission(item)) continue;
								Element ele = this.buildElement(item, 0, 0);
								if(ele != null)
									dependRootEle.addContent(ele);
							}
							root.addContent(dependRootEle);
						}
					} else {
						String query = "t.parent is null ";
						if(!StringUtils.isEmpty(this.queryString)){
							query += " and " + this.queryString;
						}
						List<TreeNode> list = service.find(query);
						if(this.lazy && list.size() > 0) {
							StringBuilder sb = new StringBuilder("t.parent.id in (" + list.get(0).getId());
							for(TreeNode pn: list) {
								sb.append(",").append(pn.getId());
							}
							sb.append(")");
							List tmp = service.find("select t.parent.id, count(t.id) from " + list.get(0).getClass().getName() + " t where " + sb + " group by t.id");
							for(int i=0; i< tmp.size(); i++){
								Object[] t = (Object[])tmp.get(i);
								this.childrenElements.put((Long)t[0], ((Long)t[1]).longValue() == 0);
							}
						}
						for(Iterator<TreeNode> it=list.iterator(); it.hasNext(); ){
							TreeNode item = it.next();
							if(!this.hasPermission(item)) continue;
							Element ele = this.buildElement(item, 0, 0);
							if(ele != null)
								root.addContent(ele);
						}
					}
				}
			} else {		//对于非树型结构对象直接获取
				List<Entity> children = service.find(this.queryString);
				for(Entity child: children){
					if(!this.hasPermission(child)) continue;
					Element childEle = new Element("TreeNode");
					childEle.setAttribute("text", child.getName() == null?"--": child.getName());
					childEle.setAttribute("value", String.valueOf(child.getId()));
					if(this.checkedIds != null && this.checkedIds.length > 0){
						for(String id: checkedIds){
							if(StringUtils.equalsIgnoreCase(id, String.valueOf(child.getId()))){
								childEle.setAttribute("checked", "true");
							}
						}
					}
					root.addContent(childEle);
				}
			}
			return new Document(root);
		}
		
		/**
		 * 生成一定深度的树的节点
		 * @modify wanghaifeng Aug 11, 2006 11:47:57 PM
		 * @param item			需要转换为xml的节点或者当前节点，或者后续的父节点
		 * @param nonLazyDeepth	不需要lazy的深度
		 * @param deepth		当前深度
		 * @return
		 * @throws Exception
		 */
		public Element buildElement(TreeNode item, int nonLazyDeepth, int deepth) throws Exception{
			Element ele = new Element("TreeNode");
			ele.setAttribute("text", item.getName()==null?"":item.getName());
			ele.setAttribute("value", String.valueOf(item.getId()));
			if(this.checkedIds != null && this.checkedIds.length > 0){
				for(String id: checkedIds){
					if(StringUtils.equalsIgnoreCase(id, String.valueOf(item.getId()))){
						ele.setAttribute("checked", "true");
					}
				}
			}
			if(this.lazy && deepth >= nonLazyDeepth){
				Boolean b = this.childrenElements.get(item.getId());
				if(b != null && !b.booleanValue())
					ele.setAttribute("src",  this.encodeURL()+"&root=" + item.getId());
			}
			TreeService treeService = (TreeService)service;
			boolean hasChildren = false;
			if(!this.lazy || deepth < nonLazyDeepth){
				List<TreeNode> children = treeService.findChildren(item, this.queryString);
				//
				if(children.size() > 0) {
					StringBuilder sb = new StringBuilder("t.parent.id in (" + children.get(0).getId());
					for(TreeNode pn: children) {
						sb.append(",").append(pn.getId());
					}
					sb.append(")");
					List tmp = service.find("select t.parent.id, count(t.id) from " + children.get(0).getClass().getName() + " t where " + sb + " group by t.id");
					for(int i=0; i< tmp.size(); i++){
						Object[] t = (Object[])tmp.get(i);
						this.childrenElements.put((Long)t[0], ((Long)t[1]).longValue() == 0);
					}
				}
				//
				hasChildren = children.size() > 0;
				for(TreeNode child: children ){
					if(!this.hasPermission(child)) continue;
					Element childElement = buildElement(child, nonLazyDeepth, deepth + 1);
					if(childElement != null){
						ele.addContent(childElement);
					}
				}
			}
			if(hasChildren && ele.getChildren().isEmpty() && ignoreEmptyParent){
				return null;
			} else {
				return ele;
			}
		}
	}
	
	public void test() throws Exception{
		TreeXmlUtils utils = new TreeXmlUtils(0, "ConfigurationTypeService", "", "", null);
		Document doc = utils.buildDocument();
		XMLOutputter outputer = new XMLOutputter();
//		comment bacause of jdom version
		//outputer.setEncoding("gbk");
		outputer.output(doc, System.out);
	}
	
	public static void main(String[] args) throws Exception{
		TreeXmlServlet xml = new TreeXmlServlet();
		xml.test();
	}
}
